﻿using Microsoft.Office.Interop.Visio;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using VisioERLeveller.Model;

namespace VisioERLeveller.Visio {

    public static class Visio2013ERDialet {

        public static VisioReader CreateReader(string filepath, string pageName) {
            return new VisioReader(filepath, pageName) {
                IDExtractor = IDExtractor,
                NameExtractor = NameExtractor,
                TextExtractor = TextExtractor,
                NullableLinkJudger = NullableLinkJudger,
                NodeJudger = NodeJudger,
                LinkJudger = LinkJudger,
                FromIDFinder = FromIDFinder,
                ToIDFinder = ToIDFinder,
            };
        }

        private static readonly Regex CRLN = new(@"\r?\n");

        private static readonly Regex ID_Extractor = new(@"^_XFTRIGGER\((.+?)\!EventXFMod\)$");

        private static string IDExtractor(Shape shape) {
            var id = shape.ID.ToString();
            return id;
        }

        private static string NameExtractor(Shape shape) {
            var name = shape.NameU;
            return name;
        }

        private static string TextExtractor(Shape shape) {
            var name = shape.Text;
            name = CRLN.Replace(name, string.Empty);
            return name;
        }

        private static bool NullableLinkJudger(Shape shape) {
            var cell = shape.Cells["EndArrow"];
            if (cell is null) return false;
            var value = cell.ResultInt[VisUnitCodes.visNumber, 0];
            return value == 14;
        }

        private static bool NodeJudger(Shape shape) {
            var name = shape.NameU;
            return name.StartsWith("Rectangle");
        }

        private static bool LinkJudger(Shape shape) {
            var name = shape.NameU;
            return name.StartsWith("Dynamic connector");
        }

        private static string FromIDFinder(Shape shape, Graph graph)
            => IDFinder(shape, graph, "Beg", "Begin");

        private static string ToIDFinder(Shape shape, Graph graph) 
            => IDFinder(shape, graph, "End", "End");

        private static string IDFinder(Shape shape, Graph graph, string triggerPrefix, string xyPrefix) {
            // 只通过 Trigger 去取的时候, 会产生 nameU 重名的情况, 需要进一步结合几何相连的关系去最终判断正确的目标 ID
            var cell = shape.Cells[$"{triggerPrefix}Trigger"];
            if (cell is null) return null;
            var formula = cell.FormulaU;
            var match = ID_Extractor.Match(formula);
            if (!match.Success) return null;
            var name = match.Groups[1].Value;
            var nodes = graph.GetNodesByName(name);
            if (nodes.Count() == 0) return null;

            // 如果只有一个就直接返回, 否则需要进一步根据空间几何关系判断到底是哪个node
            var node = nodes.Count() == 1 ?
                nodes.FirstOrDefault() :
                FindNearestNode(nodes, shape, xyPrefix);
            return node?.ID;
        }

        private static Node FindNearestNode(IEnumerable<Node> nodes, Shape link, string xyPrefix) {
            var Px = ReadCellValue(link, $"{xyPrefix}X", VisUnitCodes.visMillimeters);
            var Py = ReadCellValue(link, $"{xyPrefix}Y", VisUnitCodes.visMillimeters);

            foreach (var node in nodes) {
                var shape = node.Tag<Shape>();
                var px = ReadCellValue(shape, $"PinX"   , VisUnitCodes.visMillimeters);
                var py = ReadCellValue(shape, $"PinY"   , VisUnitCodes.visMillimeters);
                var x  = ReadCellValue(shape, $"LocPinX", VisUnitCodes.visMillimeters);
                var y  = ReadCellValue(shape, $"LocPinY", VisUnitCodes.visMillimeters);
                var w  = ReadCellValue(shape, $"Width"  , VisUnitCodes.visMillimeters);
                var h  = ReadCellValue(shape, $"Height" , VisUnitCodes.visMillimeters);
                if (!Touched(px - x, py - y, w, h, Px, Py))
                    continue;
                return node;
            }
            return null;
        }

        private static bool Touched(double x, double y, double w, double h, double Px, double Py) {
            double xmin = x, xmax = x + w;
            double ymin = y, ymax = y + h;

            static bool onSegment(double x0, double y0, double x1, double y1, double xp, double yp) {
                Point p0 = new(x0, y0);
                Point p1 = new(x1, y1);
                Point p = new(xp, yp);
                if (p0 == p1)
                    return p == p0;
                
                var p0p = p - p0;
                var p0p1 = p1 - p0;
                if (p0p.Direction != p0p1.Direction)
                    return false;

                return p0p.Module < p0p1.Module + Point.EPS;
            }

            if (onSegment(xmin, ymin, xmax, ymin, Px, Py)) return true;
            if (onSegment(xmax, ymin, xmax, ymax, Px, Py)) return true;
            if (onSegment(xmax, ymax, xmin, ymax, Px, Py)) return true;
            if (onSegment(xmin, ymax, xmin, ymin, Px, Py)) return true;

            return false;
        }

        private static double ReadCellValue(Shape shape, string cellName, VisUnitCodes units) {
            var cell = shape.Cells[cellName];
            if (cell is null) return 0;
            var value = cell.Result[units];
            return value;
        }
    }
}
